
/**
 * 成员变量和局部变量
 */
class Person {
    public String name; // 定义一个实例变量
    public static int eyeNum;   // 定义一个类变量

    /**
     * 当系统加载类或创建该类的实例时，系统自动为成员变量分配内存空间，并在分配内存空间后，自动为成员变量指定初始值。
     */
    public static void PersonTest1() {
        // 第一次主动使用Person类，该类自动初始化，则eyeNum变量开始起作用，输出0
        System.out.println("Person的eyeNum类变量值：" + Person.eyeNum);

        // 创建Person对象
        Person p = new Person();
        System.out.println("p变量的name变量值是：" + p.name + " p对象的eyeNum变量值是：" + p.eyeNum);

        p.name = "孙悟空";
        p.eyeNum = 2;
        System.out.println("p变量的name变量值是：" + p.name + " p对象的eyeNum变量值是：" + p.eyeNum);

        Person p2 = new Person();
        System.out.println("p2对象的eyeNum类变量值：" + p2.eyeNum);
        /*
         * 从上面程序来看，成员变量无须显式初始化，只要为一个类定义了类变量或实例变量，
         * 系统就会在这个类的准备阶段或创建该类的实例时进行默认初始化，
         * 成员变量默认初始化时的赋值规则与数组动态初始化时数组元素的赋值规则完全相同。
         */
    }

    /**
     * 局部变量定义后，必须经过显式初始化后才能使用，系统不会为局部变量执行初始化。
     * 这意味着定义局部变量后，系统并未为这个变量分配内存空间，
     * 直到等到程序为这个变量赋初始值时，系统才会为局部变量分配内存，并将初始值保存到这块内存中。
     * <p>
     * 与成员变量不同，局部变量不属于任何类或实例，因此它总是保存在其所在方法的栈内存中。
     * 如果局部变量是基本类型的变量，则直接把这个变量的值保存在该变量对应的内存中；
     * 如果局部变量是一个引用类型的变量，则这个变量里存放的是地址，通过该地址引用到该变量实际引用的对象或数组。
     * <p>
     * 栈内存中的变量无须系统垃圾回收，往往随方法或代码块的运行结束而结束。
     * 因此，局部变量的作用域是从初始化该变量开始，直到该方法或该代码块运行完成而结束。
     * 因为局部变量只保存基本类型的值或者对象的引用，因此局部变量所占的内存区通常比较小。
     */
    public static void PersonTest2() {
        {
            // 定义一个代码块局部变量a
            int a;
            // 下面代码将出现错误，因为a变量还未初始化
//            System.out.println("代码块局部变量a的值：" + a);
            // 为a变量赋值，也就是进行初始化
            a = 5;
            System.out.println("代码块局部变量a的值：" + a);
        }

        // 下面试图访问的a变量并不存在
//        System.out.println(a);
        // 从上面代码中可以看出，只要离开了代码块局部变量所在的代码块，这个局部变量就立即被销毁，变为不可见。
    }

    public static void main() {
        PersonTest1();
        PersonTest2();
    }
}

/**
 * 对Java初学者而言，何时应该使用类变量？何时应该使用实例变量？何时应该使用方法局部变量？何时应该使用代码块局部变量？
 * 这种选择比较困难，如果仅就程序的运行结果来看，大部分时候都可以直接使用类变量或者实例变量来解决问题，无须使用局部变量。
 * 但实际上这种做法相当错误，因为定义一个成员变量时，成员变量将被放置到堆内存中，
 * 成员变量的作用域将扩大到类存在范围或者对象存在范围，
 * 这种范围的扩大有两个害处。
 * ➢ 增大了变量的生存时间，这将导致更大的内存开销。
 * ➢ 扩大了变量的作用域，这不利于提高程序的内聚性。
 * 对比下面三个程序。
 */
class ScopeTest1 {
    static int i;

    public static void main() {
        for (i = 0; i < 10; i++) {
            System.out.println("Hello");
        }
    }
}

class ScopeTest2 {
    public static void main() {
        int i;
        for (i = 0; i < 10; i++) {
            System.out.println("Hello");
        }
    }
}

class ScopeTest3 {
    public static void main() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Hello");
        }
    }
}

public class ClassVar {
    public static void ScopeTest() {
        ScopeTest1.main();
        ScopeTest2.main();
        ScopeTest3.main();
        /*
         * 这三个程序的运行结果完全相同，但程序的效果则大有差异。
         * 第三个程序最符合软件开发规范：对于一个循环变量而言，只需要它在循环体内有效，
         * 因此只需要把这个变量放在循环体内（也就是在代码块内定义），从而保证这个变量的作用域仅在该代码块内。
         *
         * 如果有如下几种情形，则应该考虑使用成员变量。
         * ➢ 如果需要定义的变量是用于描述某个类或某个对象的固有信息的，这种变量应该定义为成员变量。
         * 如果这种信息对这个类的所有实例完全相同，或者说它是类相关的，应该定义成类变量；
         * 如果这种信息是实例相关的，例如人的身高、体重等，每个人实例的身高、体重可能互不相同，这种信息是实例相关的，因此应该定义成实例变量。
         * ➢ 如果在某个类中需要以一个变量来保存该类或者实例运行时的状态信息，这种用于保存某个类或某个实例状态信息的变量通常应该使用成员变量。
         * ➢ 如果某个信息需要在某个类的多个方法之间进行共享，则这个信息应该使用成员变量来保存。
         *
         * 即使在程序中使用局部变量，也应该尽可能地缩小局部变量的作用范围，局部变量的作用范围越小，它在内存里停留的时间就越短，程序运行性能就越好。
         * 因此，能用代码块局部变量的地方，就坚决不要使用方法局部变量。
         */
    }

    public static void main(String[] args) {
        Person.main();
        ScopeTest();
    }
}
